home *** CD-ROM | disk | FTP | other *** search
- How To Disassemble A Windows Program
-
- After we've found and analyzed WinMain() (-> lesson 1), the next
- places to inspect when you crack a program are the windows procedures
- and dialog procedures (this is true only for Windows *programs*; for
- DLL, on the countrary, the cracking procedures are different and the
- relvant techniques will be discussed in another lesson).
-
- These WndProcs and DialogProcs are "callback" procedures: they are
- *exported* from Windows executables, almost as the program were a DLL,
- so that Windows can call them.
-
- And -hear, hear!- beacuse they are exported these crucial procedures
- have *names* (almost always useful) that are accessible to any decent
- Windows disassembler. In Taskman.lst, for example, WCB clearly
- identifies TASKMANDLGPROC:
-
- Exported names by location:
- 1:007B 1 TASKMANDLGPROC <- It's a DialogProc !
-
- It works out well that the WndProcs and DialogProcs show up so nicely
- in the disassembled listings, because, as we know from Windows
- programming, these subroutines are "where the action is" in event
- driven Windows applications... or at least where the action begins.
-
- Furthermore we know that these subroutines will be most likely little
- more than (possibly very large) message handling switch/case
- statements. These usually look something like this: long FAR PASCAL
- _export WndProc(HWND hWnd, WORD message, WORD wParam, LONG lPAram)
-
- long FAR PASCAL _export WndProc(HWND hWnd, WORD message, WORD
- wParam, LONG lPAram)
- { ...
- switch (message)
- {
- case WM_CREATE:
- //... handle WM_CREATE message
- break;
-
- case WM_COMMAND:
- //... handle WM_COMMAND message
- break;
- default:
- return DefWindowProc(hwnd, message, wParam, lParam);
- }
- }
-
- Wow! Yes! As you already guessed this means that... that we get
- immediately 4 parameters for EACH exported WndProc or DlgProc!
-
- Actually there's no rule that states that a Windows WndProc or DlgProc
- has to look like this... it's just that they almost always do!
-
- Here is how the parameters to the WndProc or DialogProc will appear in
- the assembly language listing (after the function prologue):
-
- long FAR PASCAL _export WndOrDialogProc(HWND hwnd, WORD
- message, WORD wParam, LONG lParam);
-
- lParam = dword ptr [bp+6]
- wParam = word ptr [bp+0Ah]
- message = word ptr [bp+0Ch]
- hWnd or hWndDlg = word ptr [bp+0Eh]
-
- With this knowledge, we can replace an otherwise meaningless [bp+0Ch]
- with a label such as "message", a [bp+0Eh] with a "hwnd" or "hwndDlg",
- and so on in *ANY* DialogProc and WndProc in *ANY* Windows program.
-
- The boilerplate nature of Windows programming greatly simplifies
- cracking. For example, here is part of our Taskman exported:
-
- The problem here, of course, is what to make of all these magic
- numbers: 0064, OO1C, 00F4 and so on... how are we going to figure out
- what these mean?
-
- DialogProc: TASKMANDLGPROC:
-
- 1.007B ; TASKMANDLGPROC
- ... (function prologue)
- 1.008A 8B760E mov si, hWndDlg ;[bp+0E]
- 1.008D 56 push si
- 1.008E 6A64 push 0064
-
- 1.0090 9AFFFF0000 call USER.GETDLGITEM
- 1.0095 8BF8 mov di, ax
- 1.0097 8B460C mov ax, message ;[bp+0C]
- 1.009A 2D1C00 sub ax, 001C
- 1.009D 7416 je 00B5
- 1.009F 2DF400 sub ax, 00F4
- 1.00A2 7436 je 00DA
- 1.00A4 48 dec ax
- 1.00A5 7503 jne 00AA
- 1.00A7 E98301 jmp 022D
-
- 1.00AA >2D5303 sub ax, 0353
- 1.00AD 7503 jne 00B2
- 1.00AF E9D602 jmp 0388
-
- 1.00B2 >E9C801 jmp 027D
-
- 1.00B5 >837E0A00 cmp word ptr wParam, 0 ;[bp+0A]
- 1.00B9 7403 je 00BE
- 1.00BB E9BF01 jmp 027D
- ...
-
- When examined via disassembled listings, Windows programs tend to
- contain a lot of "magic numbers". Of course the actual source code
- would be :
-
- * #include '<'windows.h'>' and
- * #define numeric constants for the various resources (menus,
- strings, dialog controls, etc.) that it uses.
-
- Given a disassembled listing, it should be possible to turn a lot of
- these seemingly senseless numbers back into something understandable.
-
- Let's start with the number 001C in TaskManDlgProc():
-
- 1.0097 8B460C mov ax, message ;[bp+0C]
- 1.009A 2D1C00 sub ax, 001C
- 1.009D 7416 je 00B5
-
- If AX holds the *message* parameter to TaskManDlgProc() (line
- 1.0097)... then the value 001C must be a Windows WM_ message number
- (one of those you can breakpoint to with WINICE's BMSG command, by the
- way). Looking in WINDOWS.H, we find that 0x1C is WM_ACTIVATEAPP.
-
- TaskManDlgProc() is subtracting this value from AX and then jumping
- somewhere (let's call it ON_ACTIVATEAPP) if the result is zero... i.e.
- if it is WM_ACTIVATEAPP.
-
- This is an odd way to test whether (message == WM_ACTIVATEAPP): if the
- test fails, and we do not take the jump to ON_ACTIVATEAPP, the message
- number has 1C subtracted from it... and this value must be taken
- account of by the next switch statement:
-
- 1.009F 2DF400 sub ax, 00F4 ; (+1C=110=WM_INITDIALOG)
- 1.00A2 7436 je 00DA ; jump to ON_INITDIALOG
- 1.00A4 48 dec ax ; (110+1=111=WM_COMMAND)
- 1.00A5 7503 jne 00AA ; no, go elsewhere
- 1.00A7 E98301 jmp 022D ; yes, jump to ON_COMMAND
-
- Other WndProcs & DialogProcs will contain straightforward tests,
- rather than testing via subtraction... is a matter of compiler choice.
- In any case, a WndProc or DialogProc generally contains a collection
- of handlers for different messages.
-
- In the case of TaskManDlgProc(), we can see that's handling
- WM_ACTIVATEAPP, WM_INITDIALOG and WM_COMMAND. By itself, this
- information is rather boring... however, it tells us what is happening
- *elsewhere* in the function: 1.00B5 must be handling WM_ACTIVATEAPP
- messages (therefore let's call it ON_ACTIVATEAPP), 1.00DA must be
- handling WM_INITDIALOG, and 1.022D must be handling WM_COMMAND
- messages.
-
- Write it down! This same basic technique -find where the [bp+0Ch]
- "message" parameter to the WndProc or DialogProc is being rested, and
- from that identify the locations that handle various messages- can be
- used in *ANY* Windows program.
-
- Because handling messages is mostly what Windows applications do, once
- we know where the message handling is, we pretty much can have our way
- with the disassembled listing.
-
- Let's look now at TaskManDlgProc():
-
- TASKMANDLGPROC proc far
- ...
- DISPATCH_ON_MSG:
- 1.0097 8B460C mov ax, message ;[bp+0C]
- 1.009A 2D1C00 sub ax, WM_ACTIVATEAPP ;001C
- 1.009D 7416 je ON_ACTIVATEAPP
- 1.009F 2DF400 sub ax, 00F4 ; (+1C=110=WM_INITDIALOG)
- 1.00A2 7436 je ON_INITDIALOG
- 1.00A4 48 dec ax ;(110+1=111=WM_COMMAND)
- 1.00A5 7503 jne DEFAULT
- 1.00A7 E98301 jmp ON_COMMAND
- DEFAULT:
- 1.00AA >2D5303 sub ax, 0353 ;(111+353=464=WM_USER+64
- 1.00AD 7503 jne ON_PRIVATEMSG ;00B2= some private msg
- 1.00AF E9D602 jmp 0388
- ON_PRIVATEMSG:
- 1.00B2 >E9C801 jmp 027D
- ON_ACTIVATEAPP:
- 1.00B5 >837E0A00 cmp word ptr wParam, 0 ;[bp+0A]
- ... ; code to handle WM_ACTIVATEAPP
- ON_INITDIALOG:
- ... ; code to handle WM_INITDIALOG
- ON_COMMAND:
- ... ; code to handle WM_COMMAND
- 1.022D >8B460A mov ax, wParam ;[bp+0A]
- 1.0230 3D6800 cmp ax, 0068 ; ? What's this ?
- 1.0233 7503 jne 0238
- 1.0235 E93301 jmp 036B
- ...
-
- This is starting to look pretty reasonable. In particular, once we
- know where WM_COMMAND is being handled, we are well on the way to
- understand what the application does.
-
- WM_COMMAND is *very* important for understanding an application
- behavior because the handler for WM_COMMAND is where it deals with
- user commands such as Menu selections and dialog push button clicks...
- a lot of what makes an application unique.
-
- If you click on "Cascade" in Task manager, for instance, it comes as a
- WM_COMMAND, the same occurs if you click on "Tile" or "Switch To" or
- "End Task".
-
- An application can tell which command a user has given it by looking
- in the wParam parameter to the WM_COMMAND message.
-
- This is what we started to see at the ned of the TaskManDlgProc()
- exerpt:
-
- ; We are handling WM_COMMAND, therefore wParam is here idItem,
- ; i.e. a control or menu item identifier
- 1.022D >8B460A mov ax, wParam ;[bp+0A]
- 1.0230 3D6800 cmp ax, 0068 ;ID number for a dialog control
- 1.0233 7503 jne 0238
- 1.0235 E93301 jmp 036B
-
- 1.0238 >7603 jbe 023D
- 1.023A E96001 jmp 039D
-
- 1.023D >FEC8 dec al ;1
- 1.023F 7420 je 0261 ;if wParam==1 goto 1.0261
- 1.0241 FEC8 dec al ;1+1=2
- 1.0243 7503 jne 0248
- 1.0245 E94701 jmp 038F ;if wParam==2 goto 1.038F
-
- 1.0248 >2C62 sub al, 62 ;2+62=64
- 1.024A 742A je 0276
- 1.024C FEC8 dec al ;64+1=65
- 1.024E 7432 je 0282
- 1.0250 2C01 sub al, 01 ;65+1=66
- 1.0252 7303 jnb 0257
- 1.0254 E94601 jmp 039D
-
- 1.0257 >2C01 sub al, 01 ;66+1=67
- 1.0259 7703 ja 025E
- 1.025B E9D200 jmp 0330
-
- It's clear that wParam is being compared (in an odd subtraction way)
- to valus 1,2,65,66 and 67. What's going on?
-
- The values 1 and 2 are standard dialog button IDs:
-
- #define IDOK 1
- #define IDCANCEL 2
-
- Therefore we have here the two "classical" push buttons:
-
- 1.023D >FEC8 dec al ; 1 = OK
- 1.023F 7420 je ON_OK ; If 1 goto 1.0261= ON_OK
- 1.0241 FEC8 dec al ; 1+1=2= CANCEL
- 1.0243 7503 jne NOPE ; goto neither OK nor CANCEL
- 1.0245 E94701 jmp ON_CANCEL ; if 2 goto 1.038F= ON_CANCEL
-
- The numbers 65, 66 etc are specific to TaskManager however, we will
- not find them inside WINDOWS.H... so there is no home to find the
- names of the commands to which these magic number correspond, unless
- we happen to have a debug version of the program true? NO! FALSE!
-
- One of the notable things about Windows is that remarkably little
- information is lost or thrown away compiling the source code. These
- magic numbers seem to correspond in some way to the different Task
- Manager push buttons... it's pretty obvious that there must be a way
- of having applications tell Windows what wParam they want sent when
- one of their buttons is clicked or when one of their menu items is
- selected.
-
- Applications almost always provide Windows with this information in
- their resources (they could actually define menus and controls
- dynamycally, on the fly, but few applications take advantage of this).
- These resources are part of the NE executable and are available for
- our merry snooping around.
-
- This inspections of the resources in an EXE file is carried out by
- means of special utilities, like RESDUMP, included with Windows source
- (-> in my tool page). For example (I am using "-verbose" mode):
-
- DIALOG 10 (0Ah), "Task List" [ 30, 22,160,107]
- FONT "Helv"
- LISTBOX 100 (64h), "" [ 3, 3,154, 63]
- DEFPUSHBUTTON 1 (01h), "&Switch To" [ 1, 70, 45, 14]
- PUSHBUTTON 101 (65h), "&End Task" [ 52, 70, 45, 14]
- PUSHBUTTON 2 (02h), "Cancel" [103, 70, 55, 14]
- STATIC 99 (63h), "" [ 0, 87,160, 1]
- PUSHBUTTON 102 (66h), "&Cascade" [ 1, 90, 45, 14]
- PUSHBUTTON 103 (67h), "&Tile" [ 52, 90, 45, 14]
- PUSHBUTTON 104 (68h), "&Arrange Icons" [103, 90, 55, 14]
-
- YEAH! It's now apparent what the numbers 64h, 65h etc. mean. Imagine
- you would write Taskmanager yourself... you would write something on
- these lines:
-
- #define IDD_SWITCHTO IDOK
- #define IDD_TASKLIST 0x64
- #define IDD_ENDTASK 0x65
- #define IDD_CASCADE 0x66
- #define IDD_TILE 0x67
- #define IDD_ARRANGEICONS 0x68
-
- Let's look back at the last block of code... it makes now a lot more
- sense:
-
- ON_COMMAND:
- ; We are handling WM_COMMAND, therefore wParam is here idItem,
- ; i.e. a control or menu item identifier
- 1.022D >8B460A mov ax, wParam ;[bp+0A]
- 1.0230 3D6800 cmp ax, 0068 ;is it the ID 68h?
- ...
- 1.023D >FEC8 dec al ;1=IDOK=IDD_SWITCHTO
- 1.023F 7420 je ON_SWITCHTO ;0261
- 1.0241 FEC8 dec al ;1+1=2=ID_CANCEL
- 1.0243 7503 jne neither_OK_nor_CANCEL ;0248
- 1.0245 E94701 jmp ON_CANCEL ;038F
- neither_OK_nor_CANCEL:
- 1.0248 >2C62 sub al, 62 ;2+62=64= IDD_TASKLIST
- 1.024A 742A je ON_TASKLIST ;0276
- 1.024C FEC8 dec al ;64+1=65= IDD_ENDTASK
- 1.024E 7432 je ON_ENDTASK ;0282
- 1.0250 2C01 sub al, 01 ;65+1=66= IDD_CASCADE
- 1.0252 7303 jnb check_for_TILE ;0257
- 1.0254 E94601 jmp 039D ;something different
- check_for_TILE:
- 1.0257 >2C01 sub al, 01 ;66+1=67= IDD_TILE
- 1.0259 7703 ja 025E ;it's something else
- 1.025B E9D200 jmp ON_TILE_or_CASCADE ;0330
-
- In this way we have identified location 0330 as the place where
- Taskman's "Cascade" and "Tile" buttons are handled... we have renaimed
- it ON_TILE_or_CASCADE... let's examine its code and ensure it makes
- sense:
-
- ON_TILE_or_CASCADE:
- 1.0330 >56 push hwndDlg ;si
- 1.0331 6A00 push 0000
- 1.0333 9A6F030000 call USER.SHOWWINDOW
-
- 1.0338 9A74030000 call USER.GETDESKTOPWINDOW
- 1.033D 8BF8 mov di, ax ;hDesktopWnd
- 1.033F 837E0A66 cmp word ptr wParam, 0066 ;IDD_CASCADE
- 1.0343 750A jne ON_TILE ;034F
- 1.0345 57 push di ;hDesktopWnd
- 1.0346 6A00 push 0000
- 1.0348 9AFFFF0000 call USER.CASCADECHILDWINDOWS
- 1.034D EB2F jmp 037E
- ON_TILE:
- 1.034F >57 push di
- 1.0350 6A10 push 0010
- 1.0352 9AFFFF0000 call USER.GETKEYSTATE
- 1.0357 3D0080 cmp ax, 8000
- 1.035A 7205 jb 0361
- 1.035C B80100 mov ax, 0001 ;1= MDITILE_HORIZONTAL
- 1.035F EB02 jmp 0363
-
- 1.0361 >2BC0 sub ax, ax ;0= MDITILE_VERTICAL
-
- 1.0363 >50 push ax
- 1.0364 9AFFFF0000 call USER.TILECHILDWINDOWS
- 1.0369 EB13 jmp 037E
-
- Yes, it makes a lot of sense: We have found that the "Cascade" option
- in Tile manager, after switching through the usual bunch of
- switch/case loops, finally ends up calling an undocumented Windows API
- function: CascadeChildWindows()... similarly, the "Tile" routine ends
- up calling TileChildWindow().
-
- One thing screams for attention in the disassembled listing of
- ON_TILE: the call to GetKeyState().
-
- As an example of the kind of information you should be able to gather
- for each of these functions, if you are serious about cracking, I'll
- give you now here, in extenso, the definition from H. Schildt's
- "General purpose API functions", Osborne's Windows Programming Series,
- Vol. 2, 1994 edition (I found both this valuable book and its
- companion: volume 3: "Special purpose API functions", in a second hand
- shop, in february 1996, costing the equivalent of a pizza and a
- beer!). Besides this function is also at times important for our
- cracking purposes, and represents therefore a good choice. Here the
- description from pag.385:
-
- void GetKeyState(int iVirKey)
-
- Use GetKeyState() to determine the up, down or toggled status of
- the specified virtual key. iVirKey identifies the virtual key. To
- return the status of a standard alphanumeric character in the
- range A-Z, a-z or 0-9, iVirKey must be set equal to its ANSI
- ASCII value. All other key must use their related virtual key
- codes. The function returns a value indicating the status of the
- selected key. If the high-order bit of the byte entry is 1, the
- virtual key is pressed (down); otherwise it is up. If you examine
- a byte emlement's low-order bit and find it to be 1, the virtual
- key has been toggled. A low-order bit of 0 indicates that the key
- is untoggled.
-
- Under Windows NT/Win32, this function returns type SHORT.
-
- Usage:
-
- If your application needs to distinguish wich ALT, CTRL, or SHIFT
- key (left or right) has been pressed, iVirKey can be set equal to
- one of the following:
-
- VK_LMENU VK_RMENU
- VK_LCONTROL VK_RCONTROL
- VK_LSHIFT VK_RSHIFT
-
- Setting iVirKey equal to VK_MENU, VK_CONTROL or VK_SHIFT
- instructs GetKeyState() to ignore left and right, and only to
- report back the status of teh virtual key category. This ability
- to distinguish among virtual-key states is only available with
- GetKeyState() and the related functions listed below.
-
- The following fragment obtains the state of the SHIFT key:
-
- if(GetKeyState(VK_SHIFT) {
- ...
- }
-
- Related Functions:
-
- GetAsyncKeyState(), GetKeyboardState(), MapVirtualKey(),
- SetKeyboardState()
-
- Ok, let's go on... so we have in our code a "funny" call to
- GetKeyState(). Because the Windows USer's Guide says nothing about
- holding down a "state" (shift/ctrl/alt) key while selecting a button,
- this sounds like another undocumented "goodie" hidden inside TASKMAN.
-
- Indeed, if you try it out on the 3.1 Taskman, you'll see that clicking
- on the Tile button arranges all the windows on the desktop side by
- side, but if you hold down the SHIFT key while clicking on the Tile
- button, the windows are arranged in a stacked formation.
-
- To summarize, when the 3.1. Taskman Tile button is selected, the code
- that runs in response looks like this:
-
- Tile:
- ShowWindow(hWndDlg, SW_HIDE); // hide TASKMAN
- hDesktopWnd = GetDesktopWindow();
- if (GetKeyState(VK_SHIFT) == 0x8000)
- TileChildWindows(hDesktopWnd, MDITILE_HORIZONTAL);
- else
- TileChildWindows(hDesktopWnd, MDITILE_VERTICAL);
-
- Similarly, the CASCADE option in 3.1. TASKMAN runs the following code:
-
- Cascade:
- ShowWindow(hWndDlg, SW_HIDE); // hide TASKMAN
- CAscadeChildWindows(GetDesktopWindow(), 0);
-
- We can then proceed through each TASKMAN option like this, rendering
- the assembly language listing into more concise C.
-
- The first field to examine in TASKMAN is the Task List itself: how is
- the "Task List" Listbox filled with the names of each running
- application?
-
- What the List box clearly shows is a title bar for each visible top
- level window, and the title bar is undoubtedly supplied with a call to
- GetWindowText()... a function that obtains a copy of the specified
- window handle's title.
-
- But how does TASKMAN enumerate all the top-level Windows? Taskman
- exports TASKMANDLGPROC, but does not export any enumeration procedure.
-
- Most of the time Windows programs iterate through all existing windows
- by calling EnumWindows(). Usually they pass to this function a pointer
- to an application-supplied enumeration function, which therefore MUST
- be exported. This callback function must have following prototype:
-
- BOOL CALLBACK EnumThreadCB(HWND hWnd, LPARAM lParam)
-
- Of course, the name a programmer chooses for such an exported function
- is arbitrary. hWnd will receive the handle of each thread-associated
- window.lParam receives lAppData, a 32-bit user- defined value. This
- exported function must return non-zero to receive the next enumerated
- thread-based window, or zero to stop the process.
-
- But here we DO NOT have something like TASKMANENUMPROC in the list of
- exported functions... what's going on? Well... for a start TASKMAN IS
- NOT calling EnumWindows()... Taskman uses a GetWindow() loop to fill
- the "Task List" list box, study following C muster, sipping a good
- cocktail and comparing it with the disassembled code you have printed:
-
- Task List:
- listbox = GetDlgItem(hWndDlg, IDD_TASKLIST);
- hwnd = GetWindow(hwndDlg, GW_HWNDFIRST);
- while (hwnd)
- { if ((hwnd != hwndDlg) && //excludes self from list
- IsWindowVisible(hwnd) &&
-
- GetWindow(hwnd, GW_OWNER))
- { char buf[0x50];
- GetWindowText(hwnd, buf, 0x50); // get titlebar
- SendMessage(listbox, LB_SETITEMDATA,
- SendMessage(listbox, LB_ADDSTRING, 0, buf),
- hwnd); // store hwnd as data to go
- } // with the titlebar string
- hwnd = GetWindow(hwnd, GW_HWNDNEXT);
- }
- SendMessage(lb, LB_SETCURSEL, 0, 0); // select first item
-
- The "End Task" opton in Taskman just sends a WM_CLOSE message to the
- selected window, but only if it's not a DOS box. TASKMAN uses the
- undocumented IsWinOldApTask() function, in combination with the
- documented GetWindowTask() function, to determine if a given HWND
- corresponds to a DOS box:
-
- End Task:
- ... // boring details omitted
- if(IsWinOldApTask(GetWindowTask(hwndTarget)))
- MaybeSwitchToSelecetedWindow(hwndTarget);
-
- if(IsWindow(hwndTarget) &&
- (! (GetWindowLong(hwndTarget, GWL 5STYLE) & WS_DISABLED))
- {
- PostMessage(hwndTarget, WM_CLOSE, 0, 0);
- }
-
- The "Arrange Icons" option simply runs the documented
- ARrangeIconicWindows() function:
-
- Arrange Icons:
- Showwindow(hWndDlg, SW_HIDE);
- ArrangeIconiCWindows(GetDesktopWindow());
-
- The "Switch To" option in TASKMAN is also interesting. Like "Tile" and
- "Cascade", this too it's just a user-interface covering an
- undocupented Windows API function, in this case SwitchToThisWindow().
-
- Let's walk through the process of deciphering a COMPLETELY unlabelled
- Windows disassembly listing, that will be most of the time your
- starting situation when you crack, and let's turn it into a labelled C
- code.
-
- By the way, there does exist an interesting school of research, that
- attempts to produce an "EXE_TO_C" automatical converter. The only
- cracked version of this program I am aware of is called E2C.EXE, is
- 198500 bytes long, has been developed in 1991 by "The Austin Code
- Works and Polyglot International" in Jerusalem (Scott Guthery:
- guthery@acw.com), and has been boldly brought to the cracking world by
- Mithrandir/AlPhA/MeRCeNarY. Try to get a copy of this tool... it can
- be rather interesting for our purposes ;-)
-
- Here is the raw WCB disassembled code for a subroutine within TASKMAN,
- called from the IDD_SWITCHTO handling code in TaskManDlgProc():
-
- 1.0010 >55 push bp
- 1.0011 8BEC mov bp, sp
- 1.0013 57 push di
- 1.0014 56 push si
- 1.0015 FF7604 push word ptr [bp+04]
- 1.0018 681A04 push 041A
- 1.001B FF7604 push word ptr [bp+04]
- 1.001E 680904 push 0409
- 1.0021 6A00 push 0000
- 1.0023 6A00 push 0000
- 1.0025 6A00 push 0000
- 1.0027 9A32000000 call USER.SENDMESSAGE
- 1.002C 50 push ax
- 1.002D 6A00 push 0000
- 1.002F 6A00 push 0000
- 1.0031 9AEF010000 call USER.SENDMESSAGE
- 1.0036 8BF8 mov di, ax
- 1.0038 57 push di
- 1.0039 9A4C000000 call USER.ISWINDOW
- 1.003E 0BC0 or ax, ax
- 1.0040 742A je 006C
- 1.0042 57 push di
- 1.0043 9AFFFF0000 call USER.GETLASTACTIVEPOPUP
- 1.0048 8BF0 mov si, ax
- 1.004A 56 push si
- 1.004B 9AA4020000 call USER.ISWINDOW
- 1.0050 0BC0 or ax, ax
- 1.0052 7418 je 006C
- 1.0054 56 push si
- 1.0055 6AF0 push FFF0
- 1.0057 9ACD020000 call USER.GETWINDOWLONG
- 1.005C F7C20008 test dx, 0800
- 1.0060 750A jne 006C
- 1.0062 56 push si
- 1.0063 6A01 push 0001
- 1.0065 9AFFFF0000 call USER.SWITCHTOTHISWINDOW
- 1.006A EB07 jmp 0073
-
- 1.006C >6A00 push 0000
- 1.006E 9ABC020000 call USER.MESSAGEBEEP
-
- 1.0073 >5E pop si
- 1.0074 5F pop di
- 1.0075 8BE5 mov sp, bp
- 1.0077 5D pop bp
- 1.0078 C20200 ret 0002
-
- The RET 0002 at the end tells us that this is a near Pascal function
- that expects one WORD parameter, which appears as [bp+4] at the top of
- the code.
-
- Because [bp+4] is being used as the first parameter to SendMessage(),
- it must be an HWND of some sort.
-
- Here is the muster for SendMessage(): LRESULT SendMessage(HWND hWnd,
- UINT uMsg, WPARAM wMsgParam1, LPARAM lMsgParam2), where hWnd
- identifies the Window receiving the message, uMsg identifies the
- message being sent, wMsgParam1 & lMsgParam2 contain 16 bits and 32
- bits of message-specific information.
-
- Finally, we don't see anything being moved into AX or DX near the end
- of the function, so it looks as if this function has no return value:
-
- void near pascal some_func(HWND hwnd)
-
- Let's look once more at it... the function starts off with two nested
- calls to SendMessage (using the message numbers 41Ah and 409h). These
- numbers are greater than 400h, they must therefore be WM_USER+XX
- values. Windows controls such as edit, list and combo boxes all use
- WM_USER+XX notification codes.
-
- The only appropriate control in TASKMAN is the list box, so we can
- just look at the list of LB_XXX codes in WINDOWS.H. 1Ah is 26 decimal,
- therefore 41Ah is WM_USER+26, or LB_GETITEMDATA. Let's see what
- Osborne's "Special Purpose API functions" says about it (pag.752):
-
- LB_GETITEMDATA
- When sent: To return the value associated with a list-box item.
- wParam: Contains the index to the item in question
- lParam: Not used, must be 0
- Returns: The 32-bit value associated with the item
-
- Similarly, 409h is WM_USER+9, which in the case of a list box means
- LB_GETCURSEL. We saw earlier that TASKMAN uses LB_SETITEMDATA to store
- each window title's associated HWND. LB_GETITEMDATA will now retrive
- this hwnd:
-
- hwnd = SendMessage(listbox, LB_GETITEMDATA,
- SendMessage(listbox, LB_GETCURSEL, 0, 0), 0);
-
- Notice that now we are caling the parameter to some_func() a listbox,
- and that the return value from LB_GETITEMDATA is an HWND.
-
- How would we know it's an hwnd without our references? We can see the
- LB_GETITEMDATA return value (in DI) immediatly being passed to
- IsWindow() at line 1.0039:
-
- ; IsWindow(hwnd = SendMessage(...));
- 1.0031 9AEF010000 call far ptr SENDMESSAGE
- 1.0036 8BF8 mov di, ax
- 1.0038 57 push di
- 1.0039 9A4C000000 call far ptr ISWINDOW
-
- Next, the hwnd is passed to GetLastActivePopup(), and the HWND that
- GetLastActivePopup() returns is then checked with IsWindow()...
- IsWindow() returns non-zero if the specified hWnd is valid, and zero
- if it is invalid:
-
- ; IsWindow(hwndPopup = GetLastActivePopup(hwnd));
- 1.0042 57 push di
- 1.0043 9AFFFF0000 call USER.GETLASTACTIVEPOPUP
- 1.0048 8BF0 mov si, ax ; save hwndPopup in SI
- 1.004A 56 push si
- 1.004B 9AA4020000 call USER.ISWINDOW
-
- Next, hwndPopup (in SI) is passed to GetWindowLong(), to get
- informations about this window. Here is time to look at WINDOWS.H to
- figure out what 0FFF0h at line 1.055 and 800h at line 1.005C are
- supposed to mean:
-
- ; GetWindowLong(hwndPopup, GWL_STYLE) & WS_DISABLED
- 1.0054 56 push si ;hwndPopup
- 1.0055 6AF0 push GWL 5STYLE ;0FFF0h = -16
- 1.0057 9ACD020000 call USER.GETWINDOWLONG
- 1.005C F7C20008 test dx, 0800 ;DX:AX= 800:0= WS_DISABLED
-
- Finally, as the whole point of this exercise, assuming this checked
- window passes all its tests, its last active popup is switched to:
-
- ; SwitchToRhisWindow(hwndPopup, TRUE)
- 1.0062 56 push si ;hwndPopup
-
- 1.0063 6A01 push 0001
- 1.0065 9AFFFF0000 call USER.SWITCHTOTHISWINDOW
-
- It's here that all possible questions START: SwitchToThisWindow is not
- documented... therefore we do not know the purpose of its second
- parameter, apparently a BOOL. We cannot even tell why
- SwitchToThisWindow() is being used... when SetActiveWindow(),
- SetFocus() or BringWindowToTop() might do the trick. And why is the
- last active popup and not the window switched to?
-
- But let's resume for now our unearthed mysterious function, that will
- switch to the window selected in the Task List if the window meets all
- the function's many preconditions:
-
- void MaybeSwitchToSelectedWindow(HWND listbox)
- {
- HWND hwnd, hwndPopup;
- // first figure out wich window was selected in the Task List
- if (IsWindow(hwnd = SendMessage(listbox, LB_GETITEMDATA,
- SendMessage(listbox, LB_GETCURSEL, 0, 0), 0)))
- {
- if (IsWindow(hwndPopup = GetLastActivePopup(hwnd)))
- {
- if (! (GetWindowLong(hwndPopup, GWL_STYLE) & WS_DISABLED))
- {
- SwitchToThisWindow(hwndPopup, TRUE);
- return;
- }
- }
- MessageBeep(0); //Still here... error!
- }
-
- Now we have a good idea of what TASKMAN does (it sure took a long time
- to understand those 3K bytes of code!). In the next lessons we'll use
- what we have learned to crack together some common Windows programs.
- (->lesson 3)
-
- FraVia
-